import io.getquill.*
import io.getquill.jdbczio.Quill
import model.backoffice.BackofficeParams
import model.events.*
import model.postgresql.*
import model.vk.UserInfo
import zio.*

import java.sql.SQLException
import javax.sql.DataSource
import scala.reflect.ClassTag

object DBService {
  def getType[T](instance: T)(implicit ct: ClassTag[T]): Class[_] = ct.runtimeClass
  val ctx = new PostgresZioJdbcContext(SnakeCase)
  import ctx.*
  val zioDS: ZLayer[Any, Throwable, DataSource] =
    Quill.DataSource.fromPrefix("quill.dataSource")

  trait Service {
    def persistUsersInfo(
        userList: List[UserInfo]
    ): ZIO[DataSource, SQLException, List[Long]]

    def persistLikesAdd(
        likesAdd: List[BaseEvent]
    ): ZIO[DataSource, SQLException, List[Long]]

    def persistLikesRemove(
        likesRemove: List[BaseEvent]
    ): ZIO[DataSource, SQLException, List[Long]]

    def persistGroupLeaves(
        groupsLeaves: List[BaseEvent]
    ): ZIO[DataSource, SQLException, List[Long]]

    def persistGroupJoins(
        groupsJoins: List[BaseEvent]
    ): ZIO[DataSource, SQLException, List[Long]]

    def persistWallReposts(
        wallReposts: List[BaseEvent]
    ): ZIO[DataSource, SQLException, List[Long]]

    def persistWallRepliesNew(
        wallRepliesNew: List[BaseEvent]
    ): ZIO[DataSource, SQLException, List[Long]]

    def countLikes(params: BackofficeParams): ZIO[DataSource, SQLException, Long]

    def countUnlikes(params: BackofficeParams): ZIO[DataSource, SQLException, Long]

    def countJoins(params: BackofficeParams): ZIO[DataSource, SQLException, Long]

    def countLeaves(params: BackofficeParams): ZIO[DataSource, SQLException, Long]

    def countComments(params: BackofficeParams): ZIO[DataSource, SQLException, Long]

    def countReposts(params: BackofficeParams): ZIO[DataSource, SQLException, Long]

    def fetchLikesWithInfo(
        params: BackofficeParams
    ): ZIO[DataSource, SQLException, List[(PgLikeAdd, UserInfo)]]

    def fetchUnlikesWithInfo(
        params: BackofficeParams
    ): ZIO[DataSource, SQLException, List[(PgLikeRemove, UserInfo)]]

    def fetchJoinsWithInfo(
        params: BackofficeParams
    ): ZIO[DataSource, SQLException, List[(PgGroupJoin, UserInfo)]]

    def fetchLeavesWithInfo(
        params: BackofficeParams
    ): ZIO[DataSource, SQLException, List[(PgGroupLeave, UserInfo)]]

    def fetchCommentsWithInfo(
        params: BackofficeParams
    ): ZIO[DataSource, SQLException, List[(PgWallReplyNew, UserInfo)]]

    def fetchRepostsWithInfo(
        params: BackofficeParams
    ): ZIO[DataSource, SQLException, List[(PgWallRepost, UserInfo)]]
  }

  val live: ZLayer[Any, Nothing, DBService.Service] =
    ZLayer.succeed(new Service {

      def persistUsersInfo(
          userList: List[UserInfo]
      ): ZIO[DataSource, SQLException, List[Long]] =
        ctx.run(
          quote {
            liftQuery(userList).foreach(e => query[UserInfo].insertValue(e).onConflictIgnore)
          }
        )

      def persistLikesAdd(
          likesAdd: List[BaseEvent]
      ): ZIO[DataSource, SQLException, List[Long]] =
        ctx.run(
          quote {
            liftQuery(
              likesAdd
                .map(_.asInstanceOf[LikeAdd])
                .flatMap(event => PgLikeAdd.build(event))
            ).foreach(e => query[PgLikeAdd].insertValue(e).onConflictIgnore)
          }
        )

      def persistLikesRemove(
          likesRemove: List[BaseEvent]
      ): ZIO[DataSource, SQLException, List[Long]] =
        ctx.run(
          quote {
            liftQuery(
              likesRemove
                .map(_.asInstanceOf[LikeRemove])
                .flatMap(event => PgLikeRemove.build(event))
            ).foreach(e => query[PgLikeRemove].insertValue(e).onConflictIgnore)
          }
        )

      def persistGroupLeaves(
          groupLeaves: List[BaseEvent]
      ): ZIO[DataSource, SQLException, List[Long]] =
        ctx.run(
          quote {
            liftQuery(
              groupLeaves
                .map(_.asInstanceOf[GroupLeave])
                .flatMap(event => PgGroupLeave.build(event))
            ).foreach(e => query[PgGroupLeave].insertValue(e).onConflictIgnore)
          }
        )

      def persistGroupJoins(
          groupJoins: List[BaseEvent]
      ): ZIO[DataSource, SQLException, List[Long]] =
        ctx.run(
          quote {
            liftQuery(
              groupJoins
                .map(_.asInstanceOf[GroupJoin])
                .flatMap(event => PgGroupJoin.build(event))
            ).foreach(e => query[PgGroupJoin].insertValue(e).onConflictIgnore)
          }
        )

      def persistWallReposts(
          wallReposts: List[BaseEvent]
      ): ZIO[DataSource, SQLException, List[Long]] =
        ctx.run(
          quote {
            liftQuery(
              wallReposts
                .map(_.asInstanceOf[WallRepost])
                .flatMap(event => PgWallRepost.build(event))
                .filter(_.from_id > 0)
            ).foreach(e => query[PgWallRepost].insertValue(e).onConflictIgnore)
          }
        )

      def persistWallRepliesNew(
          wallRepliesNew: List[BaseEvent]
      ): ZIO[DataSource, SQLException, List[Long]] =
        ctx.run(
          quote {
            liftQuery(
              wallRepliesNew
                .map(_.asInstanceOf[WallReplyNew])
                .flatMap(event => PgWallReplyNew.build(event))
            ).foreach(e => query[PgWallReplyNew].insertValue(e).onConflictIgnore)
          }
        )

      def countLikes(params: BackofficeParams): ZIO[DataSource, SQLException, Long] =
        ctx.run(
          query[PgLikeAdd]
            .filter(_.timestamp > lift(params.startPeriod))
            .size
        )

      def countUnlikes(params: BackofficeParams): ZIO[DataSource, SQLException, Long] =
        ctx.run(
          query[PgLikeRemove]
            .filter(_.timestamp > lift(params.startPeriod))
            .size
        )

      def countJoins(params: BackofficeParams): ZIO[DataSource, SQLException, Long] = ctx.run(
        query[PgGroupJoin]
          .filter(_.timestamp > lift(params.startPeriod))
          .size
      )

      def countLeaves(params: BackofficeParams): ZIO[DataSource, SQLException, Long] = ctx.run(
        query[PgGroupLeave]
          .filter(_.timestamp > lift(params.startPeriod))
          .size
      )

      def countComments(params: BackofficeParams): ZIO[DataSource, SQLException, Long] = ctx.run(
        query[PgWallReplyNew]
          .filter(_.timestamp > lift(params.startPeriod))
          .size
      )

      def countReposts(params: BackofficeParams): ZIO[DataSource, SQLException, Long] = ctx.run(
        query[PgWallRepost]
          .filter(_.timestamp > lift(params.startPeriod))
          .size
      )

      def fetchLikesWithInfo(
          params: BackofficeParams
      ): ZIO[DataSource, SQLException, List[(PgLikeAdd, UserInfo)]] =
        ctx.run(quote {
          for {
            likes <- query[PgLikeAdd]
              .filter(_.timestamp > lift(params.startPeriod))
              .drop(lift(params.offset))
              .take(lift(params.limit))
            info <- query[UserInfo].join(_.id == likes.liker_id)
          } yield (likes, info)
        })

      def fetchUnlikesWithInfo(
          params: BackofficeParams
      ): ZIO[DataSource, SQLException, List[(PgLikeRemove, UserInfo)]] =
        ctx.run(quote {
          for {
            unlikes <- query[PgLikeRemove]
              .filter(_.timestamp > lift(params.startPeriod))
              .drop(lift(params.offset))
              .take(lift(params.limit))
            info <- query[UserInfo] if info.id == unlikes.liker_id
          } yield (unlikes, info)
        })

      def fetchJoinsWithInfo(
          params: BackofficeParams
      ): ZIO[DataSource, SQLException, List[(PgGroupJoin, UserInfo)]] =
        ctx.run(quote {
          for {
            joins <- query[PgGroupJoin]
              .filter(_.timestamp > lift(params.startPeriod))
              .drop(lift(params.offset))
              .take(lift(params.limit))
            info <- query[UserInfo] if info.id == joins.user_id
          } yield (joins, info)
        })

      def fetchLeavesWithInfo(
          params: BackofficeParams
      ): ZIO[DataSource, SQLException, List[(PgGroupLeave, UserInfo)]] =
        ctx.run(quote {
          for {
            leaves <- query[PgGroupLeave]
              .filter(_.timestamp > lift(params.startPeriod))
              .drop(lift(params.offset))
              .take(lift(params.limit))
            info <- query[UserInfo] if info.id == leaves.user_id
          } yield (leaves, info)
        })

      def fetchCommentsWithInfo(
          params: BackofficeParams
      ): ZIO[DataSource, SQLException, List[(PgWallReplyNew, UserInfo)]] =
        ctx.run(quote {
          for {
            comments <- query[PgWallReplyNew]
              .filter(_.timestamp > lift(params.startPeriod))
              .drop(lift(params.offset))
              .take(lift(params.limit))
            info <- query[UserInfo] if info.id == comments.from_id
          } yield (comments, info)
        })

      def fetchRepostsWithInfo(
          params: BackofficeParams
      ): ZIO[DataSource, SQLException, List[(PgWallRepost, UserInfo)]] =
        ctx.run(quote {
          for {
            reposts <- query[PgWallRepost]
              .filter(_.timestamp > lift(params.startPeriod))
              .drop(lift(params.offset))
              .take(lift(params.limit))
            info <- query[UserInfo] if info.id == reposts.from_id
          } yield (reposts, info)
        })
    })

  // persists
  def persistUsersInfo(
      userList: List[UserInfo]
  ): ZIO[DBService.Service, Throwable, List[Long]] =
    ZIO.serviceWithZIO(hasService => hasService.persistUsersInfo(userList).provideSomeLayer(zioDS))

  def persistLikesAdd(
      likesAdd: List[BaseEvent]
  ): ZIO[DBService.Service, Throwable, List[Long]] =
    ZIO.serviceWithZIO(hasService => hasService.persistLikesAdd(likesAdd).provideSomeLayer(zioDS))

  def persistLikesRemove(
      likesRemove: List[BaseEvent]
  ): ZIO[DBService.Service, Throwable, List[Long]] =
    ZIO.serviceWithZIO(hasService =>
      hasService.persistLikesRemove(likesRemove).provideSomeLayer(zioDS)
    )

  def persistGroupLeaves(
      groupLeaves: List[BaseEvent]
  ): ZIO[DBService.Service, Throwable, List[Long]] =
    ZIO.serviceWithZIO(hasService =>
      hasService.persistGroupLeaves(groupLeaves).provideSomeLayer(zioDS)
    )

  def persistGroupJoins(
      groupJoins: List[BaseEvent]
  ): ZIO[DBService.Service, Throwable, List[Long]] =
    ZIO.serviceWithZIO(hasService =>
      hasService.persistGroupJoins(groupJoins).provideSomeLayer(zioDS)
    )

  def persistWallReposts(
      wallReposts: List[BaseEvent]
  ): ZIO[DBService.Service, Throwable, List[Long]] =
    ZIO.serviceWithZIO(hasService =>
      hasService.persistWallReposts(wallReposts).provideSomeLayer(zioDS)
    )

  def persistWallRepliesNew(
      wallRepliesNew: List[BaseEvent]
  ): ZIO[DBService.Service, Throwable, List[Long]] =
    ZIO.serviceWithZIO(hasService =>
      hasService.persistWallRepliesNew(wallRepliesNew).provideSomeLayer(zioDS)
    )
  // count
  def countLikes(params: BackofficeParams): ZIO[DBService.Service, Throwable, Long] =
    ZIO.serviceWithZIO(hasService => hasService.countLikes(params).provideSomeLayer(zioDS))

  def countUnlikes(params: BackofficeParams): ZIO[DBService.Service, Throwable, Long] =
    ZIO.serviceWithZIO(hasService => hasService.countUnlikes(params).provideSomeLayer(zioDS))

  def countJoins(params: BackofficeParams): ZIO[DBService.Service, Throwable, Long] =
    ZIO.serviceWithZIO(hasService => hasService.countJoins(params).provideSomeLayer(zioDS))

  def countLeaves(params: BackofficeParams): ZIO[DBService.Service, Throwable, Long] =
    ZIO.serviceWithZIO(hasService => hasService.countLeaves(params).provideSomeLayer(zioDS))

  def countComments(params: BackofficeParams): ZIO[DBService.Service, Throwable, Long] =
    ZIO.serviceWithZIO(hasService => hasService.countComments(params).provideSomeLayer(zioDS))

  def countReposts(params: BackofficeParams): ZIO[DBService.Service, Throwable, Long] =
    ZIO.serviceWithZIO(hasService => hasService.countReposts(params).provideSomeLayer(zioDS))

  // fetch
  def fetchLikesWithInfo(
      params: BackofficeParams
  ): ZIO[DBService.Service, Throwable, List[(PgLikeAdd, UserInfo)]] =
    ZIO.serviceWithZIO(hasService => hasService.fetchLikesWithInfo(params).provideSomeLayer(zioDS))

  def fetchUnlikesWithInfo(
      params: BackofficeParams
  ): ZIO[DBService.Service, Throwable, List[(PgLikeRemove, UserInfo)]] =
    ZIO.serviceWithZIO(hasService =>
      hasService.fetchUnlikesWithInfo(params).provideSomeLayer(zioDS)
    )

  def fetchJoinsWithInfo(
      params: BackofficeParams
  ): ZIO[DBService.Service, Throwable, List[(PgGroupJoin, UserInfo)]] =
    ZIO.serviceWithZIO(hasService => hasService.fetchJoinsWithInfo(params).provideSomeLayer(zioDS))

  def fetchLeavesWithInfo(
      params: BackofficeParams
  ): ZIO[DBService.Service, Throwable, List[(PgGroupLeave, UserInfo)]] =
    ZIO.serviceWithZIO(hasService => hasService.fetchLeavesWithInfo(params).provideSomeLayer(zioDS))

  def fetchCommentsWithInfo(
      params: BackofficeParams
  ): ZIO[DBService.Service, Throwable, List[(PgWallReplyNew, UserInfo)]] =
    ZIO.serviceWithZIO(hasService =>
      hasService.fetchCommentsWithInfo(params).provideSomeLayer(zioDS)
    )

  def fetchRepostsWithInfo(
      params: BackofficeParams
  ): ZIO[DBService.Service, Throwable, List[(PgWallRepost, UserInfo)]] =
    ZIO.serviceWithZIO(hasService =>
      hasService.fetchRepostsWithInfo(params).provideSomeLayer(zioDS)
    )
}
